﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Mono.Cecil;
//------- Refl#
using Refl.Extensions;
using Refl.Meta.Compiling.Wrappers;

namespace Refl.Dreaming
{
    // Note for developers: This class (Dream) must be as light as possible, since it will be the basis of R-C# methods and functions.
    /// <summary>
    /// Dream Class. Used to store and execute methods.
    /// </summary>
    //TODO: remove System.Reflection, add Mono.Cecil
    public class Dream : Codex, IDream
    {
        //Note for developers: GLobals starting with a underscore are for internal or private use only, they should NOT be declared public.
        #region Globals
        /// <summary>
        /// The Dream configuration. Information on how should it behave lies here.
        /// </summary>
        public DreamInfo Info = null;
        //Compilation error flag.
        internal bool _HasErrors = false;
        /// <summary>
        /// {Get;} True if the compilation was unsuccessful. False otherwise.
        /// </summary>
        public bool HasErrors { get { return _HasErrors; } }
        internal List<DreamError> _Errors = null;
        /// <summary>
        /// Gets the list of errors presented by the compiler. null or empty list if there is no error.
        /// </summary>
        public List<DreamError> Errors { get { return _Errors; } }
        internal string _FName;
        public string Name { get { return Info.Name; } }
        //The list of injected variables
        internal List<Var> _Vars = new List<Var>();
        internal CompilerResults _CResults;
        //the Type instance representing the class of the dream
        internal dynamic _Instance;
        internal Type _Type;
        internal bool _TypeIsLoaded = false;
        #endregion

        /// <summary>
        /// Build a new dream receiving a Delegate as argument.
        /// </summary>
        /// <param name="code">The delegate of code</param>
        public Dream(Delegate code) : base(code) { }

        /// <summary>
        /// Build a new dream receiving a String of code, and a language as argument. Language defaults to Reflective-C# if none specified.
        /// </summary>
        /// <param name="code">The string of code</param>
        /// <param name="language">The language in which the code is written</param>
        public Dream(string code, DreamLanguage language = DreamLanguage.ReflectiveSharp) : base(code, (Language)language)
        {
            Info = new DreamInfo(DreamType.Dream);
            Info.Language = language;
            UpdateName();
        }

        /// <summary>
        /// Executes (invokes) the Dream. Return true if successful, and false otherwise.
        /// </summary>
        public bool Invoke()
        {
            if (_Errors == null) { _Errors = new List<DreamError>(); }
            if (!_isString)
            {
                try
                {
                    _Del_Code.DynamicInvoke();
                    return true;
                }
                catch (Exception ex)
                {
                    _Errors.Add(new DreamError("Dream:Invoke/DelegateInvocationFailure", "The Delegate-based Dream couldnt be invoked correctly and raised an exception: "+ex.Message,null,null,false));
                    return false;
                }
            }
            else
            {
                return InterpretInvoke();
            }
        }
        /// <summary>
        /// Dissasemble the Dream and create a file with its contents at the given file path.
        /// </summary>
        /// <param name="Output">The full file path with name and extension of the resulting source file.</param>
        public string[] Disassemble(string Output)
        {
            if (Global.IsDebug)
            {
                return ildasm.Inkove(_FName + " /output:" + Output + " /nobar /linenum");
            }
            else
            {
                return ildasm.Inkove(_FName + " /output:" + Output + " /nobar");
            }
        }
        public T RetrieveObject<T>(string name)
        {
            if (!_TypeIsLoaded) { if (!DreamWorker.TryLoadType(this)) { return default(T); } }
            object o = _Type.GetField(name).GetValue(_Instance);
            if (o.GetType() != typeof(T))
            {
                return default(T);
            }
            else
            {
                return (T)o;
            }
        }
        public dynamic RetrieveObject(string name)
        {
            if (!_TypeIsLoaded) { if (!DreamWorker.TryLoadType(this)) { return null; } }
            dynamic o = _Type.GetField(name).GetValue(_Instance);
            return o;

        }
        public void InsertObject(string name, object obj)
        {
            //Cleanup everything (code must be recompiled to include the new object)
            _TypeIsLoaded = false;
            _isCompiled = false;
            _Instance = null;
            _CResults = null;
            //adds the object to the vars list
            _Vars.Add(new Var(name, obj));
            //The code insertion will happen at Encapsulate class, and the value will be set at DreamWorker
        }

        //---------------------
        protected bool InterpretInvoke()
        {
            if (_HasErrors) { return false; }
            //If the type is not loaded, load it
            if ((_Instance == null) || (!_TypeIsLoaded))
            {
                if (!DreamWorker.TryLoadType(this))
                {
                    #if DEBUG
                        //Type could not be loaded
                        //Debugger.Break();
                    #endif
                    //just return here, an error with the problem description was aleready added to _Errors at the TryLoadType method.
                    return false;
                }
            }
            //try to invoke the dream
            try
            {
                #if DEBUG
                    MethodInfo mi = _Type.GetMethod("DreamMain");
                    mi.Invoke(_Instance, new object[0]);
                    Dummy.Nop();
                #else
                    _Type.GetMethod("DreamMain").Invoke(_Instance, new object[0]);
                #endif
            }
            catch(Exception ex)
            {
                #if DEBUG
                    Console.WriteLine();
                #endif
                _Errors.Add(new DreamError("Dream:Invoke/InvocationFailure", "Dream invocation raised an exception. Message: \"" + ex.Message + "\"", null, null, false));
                return false;
            }
            return true;
        }

        internal void UpdateName()
        {
            //the dream name is a MD5 hash of its characteristics.
            //TODO: Implement Json and generate a object for this.
            string assembled = (string.Format("[{0}][{1}][{2}][{3}]", _Str_Code, Info.Language.ToString(), Info.Threaded.ToString(), Info.AssemblyReferences.ToString()));
            Info._Name = assembled.GetMD5Hash();
        }

        #region 'Overloads'

        public static implicit operator Dream(string str)
        {
            return new Dream(str, DreamLanguage.ReflectiveSharp);
        }
        public static implicit operator Dream(Delegate mi)
        {
            return new Dream(mi);
        }

        #endregion
    }

    internal class Var
    {
        public object Object = null;
        public string Name = "";
        public Var(String name, object obj)
        {
            Name = name;
            Object = obj;
        }
    }
}